﻿//************************************************************************************************************
// CoNatural.Data.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Collections.Generic;
using System.Data;
using CoNatural.Binders;

namespace CoNatural.Data {
	/// <summary>
	/// Reader execution context
	/// </summary>
	public class ReaderExecutionContext<T> : ExecutionContextBase where T : class, new() {
		private IDataReader reader = null;

		public int ResultIndex { get; internal protected set; }
		public IDataRecord Record { get { return reader; } }
		public T Target { get; private set; }

		// cache context binders
		private const int MAXRS = 10;
		private IBinder[] tbinders = new IBinder[MAXRS]; // binders of T by resultset
		private IDictionary<int, IBinder[]> ibinders = null; // binders of !T by resultset

		/// <summary>
		/// Constructor for single reader execution
		/// </summary>
		internal ReaderExecutionContext(IConnection connection, ICommand command) : base(connection, command) {}

		/// <summary>
		/// Materializes instance of T from current record in context
		/// </summary>
		/// <returns>The new materialized instance.</returns>
		public T Materialize() {
			var binder = tbinders[ResultIndex - 1];
			if (binder == null) {
				binder = Connection.DataBinder.CreateMaterializer<T, T>(this);
				tbinders[ResultIndex - 1] = binder;
			}
			return (T)binder.Bind(Record, Target);
		}

		/// <summary>
		/// Materializes instance of I from current record in context of T
		/// </summary>
		/// <typeparam name="I">The type of the instance.</typeparam>
		/// <returns>The new materialized instance.</returns>
		public I Materialize<I>() where I : class, new() {
			// create cache on demand
			if (ibinders == null)
				ibinders = new Dictionary<int, IBinder[]>();

			IBinder[] rbinders;
			int key = typeof(I).GetHashCode();
			if (!ibinders.TryGetValue(key, out rbinders)) {
				rbinders = new IBinder[MAXRS];
				ibinders.Add(key, rbinders);
			}

			var binder = rbinders[ResultIndex - 1];
			if (binder == null) {
				binder = Connection.DataBinder.CreateMaterializer<T, I>(this);
				rbinders[ResultIndex - 1] = binder;
			}

			return (I)binder.Bind(Record, null);
		}

		#region Execution

		internal T Execute(ReaderCallback<T> callback) {
			Target = default(T);
			using (reader = DbCommand.ExecuteReader()) {
				if (!reader.IsClosed) {
					if (reader.Read()) {
						ResultIndex = 1;
						Target = callback != null ? callback(this) : Materialize();
					}
					reader.Close();
				}
			}
			reader = null;
			return Target;
		}

		internal int ExecuteReader(T target, ReaderCallback<T> callback) {
			Target = target;
			int records = 0;
			using (reader = DbCommand.ExecuteReader()) {
				if (!reader.IsClosed) {
					ResultIndex = 1;
					do {
						while (reader.Read()) {
							Target = callback != null ? callback(this) : Materialize();
							records++;
						}
						ResultIndex++;
					}
					while (reader.NextResult());
					reader.Close();
				}
			}
			reader = null;
			return records;
		}

		/// <summary>
		/// Reads data records on demand, materializing them into instances of T.
		/// </summary>
		/// <param name="callback">The type materializer.</param>
		/// <returns>IEnumerable of T.</returns>
		internal IEnumerable<T> ExecuteReader(ReaderCallback<T> callback) {
			// open reader
			try {
				Open();
				reader = DbCommand.ExecuteReader();
			}
			catch (Exception ex) {
				HandleError(ex);
				yield break;
			}

			bool more = false, read = false;
			ResultIndex = 1;

			if (!reader.IsClosed) {
				do {
					do {
						// hydrate t with next record 
						T t = default(T);
						try {
							read = reader.Read();
							if (read)
								t = callback != null ? callback(this) : Materialize();
						}
						catch (Exception ex) {
							HandleError(ex);
							yield break;
						}

						// yield result (yield cannot be inside try/catch block)
						if (read && t != null)
							yield return t;
					}
					while (read);

					// check if there are more result sets
					try {
						more = reader.NextResult();
						ResultIndex++;
					}
					catch (Exception ex) {
						HandleError(ex);
						yield break;
					}
				}
				while (more);
			}
		}

		#endregion Execution

		#region DbCommand Management

		protected override void CloseDbCommand() {
			if (reader != null) {
				try {
					reader.Close();
					reader.Dispose();
				}
				catch (Exception ex) {
					HandleError(ex);
				}
				reader = null;
			}
			base.CloseDbCommand();
		}

		#endregion DbCommand Management
	}
}
